#include "Url.hpp"
enum {
	URL_SCHEME = 0, 
	URL_USER, 
	URL_PASS, 
	URL_HOST, 
	URL_PORT, 
	URL_PATH, 
	URL_QUERY, 
	URL_FRAGMENT, 
	URL_PARTS_SIZE
}; 
static size_t url_part_names[URL_PARTS_SIZE]; 

ZEND_BEGIN_ARG_INFO_EX(arginfo_url___construct, 0, 0, 0)
	ZEND_ARG_INFO(0, url)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_parse, 0, 0, 0)
	ZEND_ARG_INFO(0, url)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_addQueryValues, 0, 0, 1)
	ZEND_ARG_ARRAY_INFO(0, values, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url___isset, 0, 0, 1)
	ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url___unset, 0, 0, 1)
	ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url___get, 0, 0, 1)
	ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url___set, 0, 0, 2)
	ZEND_ARG_INFO(0, key)
	ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url___toString, 0, 0, 0)
	
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_offsetSet, 0, 0, 2)
	ZEND_ARG_INFO(0, key)
	ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_offsetExists, 0, 0, 1)
	ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_offsetUnset, 0, 0, 1)
	ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_offsetGet, 0, 0, 1)
	ZEND_ARG_INFO(0, key)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_getUrl, 0, 0, 0)
	ZEND_ARG_INFO(0, separator)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_get, 1, 0, 1)
	ZEND_ARG_INFO(0, module)
	ZEND_ARG_ARRAY_INFO(0, vars, 0)
	ZEND_ARG_INFO(0, session)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_url_setModule, 1, 0, 1)
	ZEND_ARG_INFO(0, module)
ZEND_END_ARG_INFO()

static zend_function_entry php_url_methods[] = {
	PHP_ME(Url, __construct,    arginfo_url___construct,    ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
	PHP_ME(Url, parse,          arginfo_url_parse,          ZEND_ACC_PUBLIC)
	PHP_ME(Url, addQueryValues, arginfo_url_addQueryValues, ZEND_ACC_PUBLIC)
	PHP_ME(Url, __isset,        arginfo_url___isset,        ZEND_ACC_PUBLIC)
	PHP_ME(Url, __unset,        arginfo_url___unset,        ZEND_ACC_PUBLIC)
	PHP_ME(Url, __get,          arginfo_url___get,          ZEND_ACC_PUBLIC)
	PHP_ME(Url, __set,          arginfo_url___set,          ZEND_ACC_PUBLIC)
	PHP_ME(Url, __toString,     arginfo_url___toString,     ZEND_ACC_PUBLIC)
	PHP_ME(Url, offsetSet,      arginfo_url_offsetSet,      ZEND_ACC_PUBLIC)
	PHP_ME(Url, offsetExists,   arginfo_url_offsetExists,   ZEND_ACC_PUBLIC)
	PHP_ME(Url, offsetUnset,    arginfo_url_offsetUnset,    ZEND_ACC_PUBLIC)
	PHP_ME(Url, offsetGet,      arginfo_url_offsetGet,      ZEND_ACC_PUBLIC)
	PHP_ME(Url, getUrl,         arginfo_url_getUrl,         ZEND_ACC_PUBLIC)
	PHP_ME(Url, get,            arginfo_url_get,            ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
	PHP_ME(Url, setModule,      arginfo_url_setModule,      ZEND_ACC_PUBLIC)
	PHP_FE_END
}; 

void php_url_free_storage(void *object TSRMLS_DC) {
	php_url_object *obj = (php_url_object *)object; 
	if (obj -> url) {
		delete obj -> url; 
		obj -> url = NULL; 
	}
	zend_hash_destroy(obj -> std.properties); 
	FREE_HASHTABLE(obj -> std.properties); 
	efree(obj); 
}

zend_object_value php_url_create_handler(zend_class_entry *type TSRMLS_DC) {
	zend_object_value retval; 
	
	php_url_object *obj = (php_url_object *)emalloc(sizeof(php_url_object)); 
	memset(obj, 0, sizeof(php_url_object)); 
	obj -> std.ce = type; 
	
	ALLOC_HASHTABLE(obj -> std.properties); 
	zend_hash_init(obj -> std.properties, 0, NULL, ZVAL_PTR_DTOR, 0); 
	object_properties_init(&(obj -> std), php_url_ce); 
	retval.handle   = zend_objects_store_put(obj, NULL, php_url_free_storage, NULL TSRMLS_CC); 
	retval.handlers = &php_url_object_handlers; 
	return retval; 
}

void init_url_preprocessor() {
	zend_class_entry ce; 
	
	// Evilworlds.Text.Url
	INIT_CLASS_ENTRY(ce, URL_CLASS_NAME, php_url_methods); 
	php_url_ce = zend_register_internal_class(&ce TSRMLS_CC); 
	php_url_ce -> create_object = php_url_create_handler;
	memcpy(&php_url_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); 
	php_url_object_handlers.clone_obj = NULL; 
	php_url_ce -> ce_flags |= ZEND_ACC_FINAL_CLASS; 
	zend_class_implements(php_url_ce TSRMLS_CC, 1, zend_ce_arrayaccess); 
	
	url_part_names[URL_SCHEME]   = hasher("scheme"); 
	url_part_names[URL_USER]     = hasher("user"); 
	url_part_names[URL_PASS]     = hasher("pass"); 
	url_part_names[URL_HOST]     = hasher("host"); 
	url_part_names[URL_PORT]     = hasher("port"); 
	url_part_names[URL_PATH]     = hasher("path"); 
	url_part_names[URL_QUERY]    = hasher("query"); 
	url_part_names[URL_FRAGMENT] = hasher("fragment"); 
}

void destroy_url_preprocessor() {
	// thing todo...
}

PHP_METHOD(Url, __construct) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	
	obj -> url = new Url(); 
	char *url; int key_len; 
	
	if (ZEND_NUM_ARGS() > 0) {
		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &url, &key_len) == SUCCESS)
			obj -> url -> parse(url, key_len); 
	}
}

PHP_METHOD(Url, parse) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	char *url; int key_len; 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &url, &key_len) != SUCCESS)
		WRONG_PARAM_COUNT; 
	obj -> url -> parse(url, key_len); 
}

PHP_METHOD(Url, offsetGet) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	char *key; int key_len; 
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) != SUCCESS)
		WRONG_PARAM_COUNT; 
	
	try {
		const string &str = (*obj -> url)[key]; 
		RETURN_STRINGL(str.c_str(), str.length(), 1); 
	} catch (out_of_range e) {
		RETURN_NULL(); 
	}
}

PHP_METHOD(Url, offsetUnset) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	char *key; int key_len; 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) != SUCCESS)
		WRONG_PARAM_COUNT; 
	obj -> url -> deleteQueryKey(key, true); 
}

PHP_METHOD(Url, offsetExists) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	char *key; int key_len; 
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) != SUCCESS)
		WRONG_PARAM_COUNT; 
	if (obj -> url -> isQueryKeyExists(key)) {
		RETURN_TRUE; 
	} else {
		RETURN_FALSE; 
	}
}

PHP_METHOD(Url, offsetSet) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	char *key, *value; int key_len, value_length; 
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len, &value, &value_length) != SUCCESS)
		WRONG_PARAM_COUNT; 
	
	if (key_len > 0) {
		(*obj -> url)[key] = value; 
		RETURN_TRUE; 
	}
	
	RETURN_FALSE; 
}

PHP_METHOD(Url, addQueryValues) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	
	zval *value; 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &value) != SUCCESS)
		WRONG_PARAM_COUNT; 
	
	HashPosition pos; 
	zval **entry; 
	char  *string_key; 
	uint   string_key_len; 
	ulong  num_key; 
	
	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(value), &pos); 
	while (zend_hash_get_current_data_ex(Z_ARRVAL_P(value), (void **)&entry, &pos) == SUCCESS) {
		if (zend_hash_get_current_key_ex(Z_ARRVAL_P(value), &string_key, &string_key_len, &num_key, 1, &pos) == HASH_KEY_IS_STRING) {
			if (Z_TYPE_PP(entry) == IS_STRING)
				(*obj -> url)[string_key] = string(Z_STRVAL_PP(entry), Z_STRLEN_PP(entry)); 
		}
		zend_hash_move_forward_ex(Z_ARRVAL_P(value), &pos); 
	}
}

PHP_METHOD(Url, __set) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	char *key; int key_len; 
	zval *value; 
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &key, &key_len, &value) != SUCCESS)
		WRONG_PARAM_COUNT; 
	size_t hash = hasher(key); 
	
	if (hash == url_part_names[URL_SCHEME] && Z_TYPE_P(value) == IS_STRING) {
		obj -> url -> setScheme(Z_STRVAL_P(value), Z_STRLEN_P(value)); 
	} else if (hash == url_part_names[URL_USER] && Z_TYPE_P(value) == IS_STRING) {
		obj -> url -> setUser(Z_STRVAL_P(value), Z_STRLEN_P(value)); 
	} else if (hash == url_part_names[URL_PASS] && Z_TYPE_P(value) == IS_STRING) {
		obj -> url -> setPassword(Z_STRVAL_P(value), Z_STRLEN_P(value)); 
	} else if (hash == url_part_names[URL_HOST] && Z_TYPE_P(value) == IS_STRING) {
		obj -> url -> setHost(Z_STRVAL_P(value), Z_STRLEN_P(value)); 
	} else if (hash == url_part_names[URL_PORT]) {
		if (Z_TYPE_P(value) == IS_LONG)
			obj -> url -> setPort(Z_LVAL_P(value)); 
		else if (Z_TYPE_P(value) == IS_STRING)
			obj -> url -> setPort(atoi(Z_STRVAL_P(value))); 
	} else if (hash == url_part_names[URL_PATH] && Z_TYPE_P(value) == IS_STRING) {
		obj -> url -> setPath(Z_STRVAL_P(value), Z_STRLEN_P(value)); 
	} else if (hash == url_part_names[URL_QUERY] && Z_TYPE_P(value) == IS_ARRAY) {
		HashPosition pos; 
		zval **entry; 
		char  *string_key; 
		uint   string_key_len; 
		ulong  num_key; 
		
		obj -> url -> getQuery().clear(); 
		zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(value), &pos); 
		while (zend_hash_get_current_data_ex(Z_ARRVAL_P(value), (void **)&entry, &pos) == SUCCESS) {
			if (zend_hash_get_current_key_ex(Z_ARRVAL_P(value), &string_key, &string_key_len, &num_key, 1, &pos) == HASH_KEY_IS_STRING) {
				if (Z_TYPE_PP(entry) == IS_STRING)
					(*obj -> url)[string_key] = string(Z_STRVAL_PP(entry), Z_STRLEN_PP(entry)); 
			}
			zend_hash_move_forward_ex(Z_ARRVAL_P(value), &pos); 
		}
	} else if (hash == url_part_names[URL_FRAGMENT] && Z_TYPE_P(value) == IS_STRING) {
		obj -> url -> setFragment(Z_STRVAL_P(value), Z_STRLEN_P(value)); 
	}
}

PHP_METHOD(Url, __isset) {
	char *key; int key_len; 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) != SUCCESS)
		WRONG_PARAM_COUNT; 
	RETURN_TRUE; 
}

PHP_METHOD(Url, __unset) {
	
}

PHP_METHOD(Url, __get) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	char *key; 
	unsigned int key_len; 
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) != SUCCESS)
		WRONG_PARAM_COUNT; 
	size_t hash = hasher(key); 
	
	if (hash == url_part_names[URL_SCHEME]) {
		RETURN_STRINGL(obj -> url -> getScheme(), obj -> url -> getSchemeLength(), 1); 
	} else if (hash == url_part_names[URL_USER]) {
		RETURN_STRINGL(obj -> url -> getUser(), obj -> url -> getUserLength(), 1); 
	} else if (hash == url_part_names[URL_PASS]) {
		RETURN_STRINGL(obj -> url -> getPassword(), obj -> url -> getPasswordLength(), 1); 
	} else if (hash == url_part_names[URL_HOST]) {
		RETURN_STRINGL(obj -> url -> getHost(), obj -> url -> getHostLength(), 1); 
	} else if (hash == url_part_names[URL_PORT]) {
		RETURN_LONG(obj -> url -> getPort()); 
	} else if (hash == url_part_names[URL_PATH]) {
		RETURN_STRINGL(obj -> url -> getPath(), obj -> url -> getPathLength(), 1); 
	} else if (hash == url_part_names[URL_QUERY]) {
		array_init(return_value); 
		const vector<Url::URIQuery> &query = obj -> url -> getQuery(); 
		for (auto x: query)
			add_assoc_stringl(return_value, x.name.c_str(), (char *)x.value.c_str(), x.value.length(), 1); 
		return; 
	} else if (hash == url_part_names[URL_FRAGMENT]) {
		RETURN_STRINGL(obj -> url -> getFragment(), obj -> url -> getFragmentLength(), 1); 
	} else
		RETURN_NULL(); 
}

PHP_METHOD(Url, get) {
	char *module; int module_len; 
	zend_bool session = true; 
	php_url_object *url_object; 
	zval *url_obj = NULL, *modules_config = NULL, *base_url = NULL; 
	zval *url_args = NULL; 
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ab", &module, &module_len, &url_args, &session) != SUCCESS)
		WRONG_PARAM_COUNT; 
	
	RETVAL_BOOL(0); 
	do {
		// new Url
		url_obj = get_class_by_name(const_str_length(URL_CLASS_NAME)); 
		if (!url_obj)
			break; 
		
		// Получение конфига common.site
		base_url = evilworlds_get_config("common", "site", "base_url"); 
		if (!base_url || Z_TYPE_P(base_url) != IS_STRING)
			break; 
		
		// Парсим base_url
		if (!call_method(url_obj, const_str_length(ZEND_CONSTRUCTOR_FUNC_NAME), NULL, 1, base_url))
			break; 
		
		url_object = (php_url_object *)zend_object_store_get_object(url_obj TSRMLS_CC); 
		if (!url_object)
			break; 
		
		RETVAL_ZVAL(url_obj, false, false); 
		
		// Получение конфига модуля
		modules_config = evilworlds_get_config("modules", module, NULL); 
		if (modules_config && Z_TYPE_P(modules_config) == IS_ARRAY) {
			// Добавляем url_prefix в наш URL
			zval **tmp = NULL; 
			if (zend_hash_find(Z_ARRVAL_P(modules_config), const_obj_size("url_prefix"), (void **)&tmp) == SUCCESS && Z_TYPE_PP(tmp) == IS_STRING) {
				url_object -> url -> setPath("/", 1); 
				if (Z_STRLEN_PP(tmp) > 0) {
					url_object -> url -> appendPath(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); 
					url_object -> url -> appendPath("/", 1); 
				}
			}
		}
		
		// Добавление сессии
		if (session) {
			zval *session_cookies = evilworlds_registry_get("session_cookies"); 
			if (!session_cookies || Z_TYPE_P(session_cookies) != IS_BOOL || !Z_BVAL_P(session_cookies)) {
				zval *session = evilworlds_registry_get("current_sess"); 
				if (session && Z_TYPE_P(session) == IS_OBJECT) {
					zval *tmp, *ret; 
					MAKE_STD_ZVAL(ret); 
					MAKE_STD_ZVAL(tmp); 
					ZVAL_STRING(tmp, "sid", 1); 
					if (call_function("get", &session, ret, 1, tmp))
						(*url_object -> url)["sid"] = Z_STRVAL_P(ret); 
					FREE_ZVAL(tmp); 
					FREE_ZVAL(ret); 
				}
			}
		}
		
		// Добавляем аргументы
		if (url_args && Z_TYPE_P(url_args) == IS_ARRAY) {
			HashPosition pos; 
			zval **entry; 
			char  *string_key; 
			uint   string_key_len; 
			ulong  num_key; 
			
			zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(url_args), &pos); 
			while (zend_hash_get_current_data_ex(Z_ARRVAL_P(url_args), (void **)&entry, &pos) == SUCCESS) {
				if (zend_hash_get_current_key_ex(Z_ARRVAL_P(url_args), &string_key, &string_key_len, &num_key, 1, &pos) == HASH_KEY_IS_STRING) {
					if (Z_TYPE_PP(entry) != IS_STRING)
						convert_to_string_ex(entry); 
					(*url_object -> url)[string_key] = string(Z_STRVAL_PP(entry), Z_STRLEN_PP(entry)); 
				}
				zend_hash_move_forward_ex(Z_ARRVAL_P(url_args), &pos); 
			}
		}
	} while (0); 
	
	if (Z_TYPE_P(return_value) != IS_OBJECT && url_obj) {
		FREE_ZVAL(url_obj); 
		url_obj = NULL; 
	}
}

PHP_METHOD(Url, setModule) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	
	char *module; int module_len; 
	bool session = true; 
	zval *modules_config = NULL, *base_url = NULL; 
	
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &module, &module_len, &session) != SUCCESS)
		WRONG_PARAM_COUNT; 
	
	RETVAL_BOOL(0); 
	do {
		RETVAL_ZVAL(object, false, false); 
		
		// Получение конфига модуля
		modules_config = evilworlds_get_config("modules", module, NULL); 
		if (modules_config && Z_TYPE_P(modules_config) == IS_ARRAY) {
			// Добавляем url_prefix в наш URL
			zval **tmp = NULL; 
			if (zend_hash_find(Z_ARRVAL_P(modules_config), const_obj_size("url_prefix"), (void **)&tmp) == SUCCESS && Z_TYPE_PP(tmp) == IS_STRING) {
				obj -> url -> setPath("/", 1); 
				if (Z_STRLEN_PP(tmp) > 0) {
					obj -> url -> appendPath(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); 
					obj -> url -> appendPath("/", 1); 
				}
			}
			if (tmp && *tmp)
				FREE_ZVAL(*tmp); 
		}
		
		// Добавление сессии
		if (session) {
			zval *session_cookies = evilworlds_registry_get("session_cookies"); 
			if (!session_cookies || Z_TYPE_P(session_cookies) != IS_BOOL || !Z_BVAL_P(session_cookies)) {
				zval *session = evilworlds_registry_get("current_sess"); 
				if (session && Z_TYPE_P(session) == IS_OBJECT) {
					zval *tmp, *ret; 
					MAKE_STD_ZVAL(ret); 
					MAKE_STD_ZVAL(tmp); 
					ZVAL_STRING(tmp, "sid", 1); 
					if (call_function("get", &session, ret, 1, tmp))
						(*obj -> url)["sid"] = Z_STRVAL_P(ret); 
					FREE_ZVAL(tmp); 
					FREE_ZVAL(ret); 
				}
			}
		}
	} while (0); 
	
	if (modules_config) {
		FREE_ZVAL(modules_config); 
		modules_config = NULL; 
	}
	
	if (base_url) {
		FREE_ZVAL(base_url); 
		base_url = NULL; 
	}
}

PHP_METHOD(Url, getUrl) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	char *separator; int separator_len; 
	if (ZEND_NUM_ARGS() > 0 && zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &separator, &separator_len) == SUCCESS) {
		const string &str = obj -> url -> toString(separator); 
		RETURN_STRINGL(str.c_str(), str.length(), 1); 
	} else {
		const string &str = obj -> url -> toString(); 
		RETURN_STRINGL(str.c_str(), str.length(), 1); 
	}
}

PHP_METHOD(Url, __toString) {
	zval *object = getThis(); 
	php_url_object *obj = (php_url_object *)zend_object_store_get_object(object TSRMLS_CC); 
	const string &str = obj -> url -> toString(); 
	RETURN_STRINGL(str.c_str(), str.length(), 1); 
}
